package com.xaaef.grpc.client.util;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.xaaef.grpc.lib.util.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

import static com.xaaef.grpc.lib.consts.GlobalConst.*;


@Slf4j
public class RestBreaker {


    public static <T> RestResponseWrapper<T> fallbackWithValue(Supplier<T> fun, T fallbackValue) {
        return fallback(fun, (e1, e2) -> fallbackValue);
    }


    public static <T> RestResponseWrapper<T> fallbackWithException(Supplier<T> fun, Function<feign.FeignException, T> fallback) {
        return fallback(fun, (e1, e2) -> fallback.apply(e1));
    }


    public static <T> RestResponseWrapper<T> fallbackWithMessage(Supplier<T> fun, Function<String, T> fallback) {
        return fallback(fun, (e1, e2) -> fallback.apply(e2));
    }


    public static <T> RestResponseWrapper<T> fallback(Supplier<T> fun) {
        return fallback(fun, (e1, e2) -> null);
    }


    public static <T> RestResponseWrapper<T> fallback(Supplier<T> fun, BiFunction<feign.FeignException, String, T> fallback) {
        try {
            final var value = fun.get();
            final var status = HttpStatus.OK;
            return RestResponseWrapper.result(status.value(), status.getReasonPhrase(), value);
        } catch (feign.FeignException ex) {
            final var errorDetails = getErrorDetails(ex);
            if (fallback == null) {
                return RestResponseWrapper.result(ex.status(), errorDetails, null);
            } else {
                return RestResponseWrapper.result(ex.status(), errorDetails, fallback.apply(ex, errorDetails));
            }
        }
    }

    /**
     * <p>
     * 获取 响应的错误信息，按照以下顺序获取
     * </p>
     * <p>
     * 1.响应头 中 包含 Error-Details
     * </p>
     * <p>
     * 2.响应头 Content-Type 为 text/plain , 直接返回 响应体 (为了兼容istio熔断，返回的错误信息)
     * </p>
     * <p>
     * 3.响应头 Content-Type 为 application/json
     * </p>
     * <ul>
     * <li>3.1 : 获取 json响应体 的 error 字段</li>
     * <li>3.2 : 获取 json响应体 的 Error 字段</li>
     * <li>3.3 : 获取 json响应体 的 message 字段</li>
     * <li>3.4 : 获取 json响应体 的 Message 字段</li>
     * </ul>
     * <p>
     * 4. feign.FeignException 的 getMessage() 方法
     * </p>
     *
     * @param feign.FeignException
     * @return String
     * @author WangChenChen
     * @version 1.0.0
     * @date 2024/4/11 上午11:34
     */
    private static String getErrorDetails(feign.FeignException ex) {
        // 获取 响应头 是否包含 Error-Details 字段
        final var errors = ex.responseHeaders().get(HEADER_ERROR_DETAILS);
        if (CollectionUtil.isNotEmpty(errors)) {
            final var errorDetails = CollectionUtil.getFirst(errors);
            return URLDecoder.decode(errorDetails, StandardCharsets.UTF_8);
        } else {
            if (ex.responseBody().isPresent()) {
                // 获取 响应头 Content-Type 类型
                final var contentTypes = ex.responseHeaders().get(HttpHeaders.CONTENT_TYPE).toArray(new String[0]);
                // 判断 响应体类型 是否为 text (为了兼容istio熔断，返回错误信息)
                if (StrUtil.equalsAnyIgnoreCase(MediaType.TEXT_PLAIN_VALUE, contentTypes)) {
                    return new String(ex.responseBody().get().array());
                }
                // 判断 响应体类型 是否为 json (为了兼容 第三方服务接口，返回错误信息)
                if (StrUtil.equalsAnyIgnoreCase(MediaType.APPLICATION_JSON_VALUE, contentTypes)) {
                    final var jsonMap = JsonUtils.toMap(ex.responseBody().get().array(), String.class, Object.class);
                    if (jsonMap.containsKey(BODY_ERROR)) {
                        return jsonMap.get(BODY_ERROR).toString();
                    }
                    if (jsonMap.containsKey(StrUtil.upperFirst(BODY_ERROR))) {
                        return jsonMap.get(StrUtil.upperFirst(BODY_ERROR)).toString();
                    }
                    if (jsonMap.containsKey(BODY_MESSAGE)) {
                        return jsonMap.get(BODY_MESSAGE).toString();
                    }
                    if (jsonMap.containsKey(StrUtil.upperFirst(BODY_MESSAGE))) {
                        return jsonMap.get(StrUtil.upperFirst(BODY_MESSAGE)).toString();
                    }
                }
            }
            return ex.getMessage();
        }
    }


}
